package com.personalkeeper;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;

public class HistoryProvider extends ContentProvider {

    public static final String DATABASE_NAME = "personalkeeper.db";
    public static final int DATABASE_VERSION = 4;
    public static final String AUTHORITY = "com.personalkeeper.history";

    private static class Tables {
        public static final String LOCATION_HISTORY = "location_history";
        public static final String MARKERS = "markers";
        public static final String NOTES = "notes";
        public static final String STATISTICS = "statistics";
        public static final String TRACKING_SESSION = "tracking_session";

        public static final String[] ALL_TABLES =
        { LOCATION_HISTORY, MARKERS, NOTES, STATISTICS, TRACKING_SESSION };
    }

    public static class LocationHistory implements BaseColumns {
        public static final Uri CONTENT_URI = Uri.parse(
            "content://" + AUTHORITY + "/" + Tables.LOCATION_HISTORY);

        public static final String CONTENT_TYPE =
        "vnd.android.cursor.dir/vnd.personalkeeper.location";
        public static final String CONTENT_ITEM_TYPE =
        "vnd.android.cursor.item/vnd.personalkeeper.location";

        public static final String LATITUDE = "latitude";
        public static final String LONGITUDE = "longitude";
        public static final String DATE = "date";

        public static final String DEFAULT_SORT_ORDER = DATE + " ASC";
    }

    public static class Notes implements BaseColumns {
        public static final Uri CONTENT_URI = Uri.parse(
            "content://" + AUTHORITY + "/" + Tables.NOTES);

        public static final String CONTENT_TYPE =
        "vnd.android.cursor.dir/vnd.personalkeeper.note";
        public static final String CONTENT_ITEM_TYPE =
        "vnd.android.cursor.item/vnd.personalkeeper.note";

        public static final String TITLE = "title";
        public static final String DESCRIPTION = "description";
        public static final String LATITUDE = LocationHistory.LATITUDE;
        public static final String LONGITUDE = LocationHistory.LONGITUDE;
        public static final String DATE = "date";
        public static final String IMAGE_PATH = "image_path";

        public static final String DEFAULT_SORT_ORDER = DATE + " ASC";
    }

    public static class Markers implements BaseColumns {
        public static final Uri CONTENT_URI = Uri.parse(
            "content://" + AUTHORITY + "/" + Tables.MARKERS);

        public static final String CONTENT_TYPE =
        "vnd.android.cursor.dir/vnd.personalkeeper.marker";
        public static final String CONTENT_ITEM_TYPE =
        "vnd.android.cursor.item/vnd.personalkeeper.marker";

        public static final String NAME = "name";
        public static final String LATITUDE = LocationHistory.LATITUDE;
        public static final String LONGITUDE = LocationHistory.LONGITUDE;
        public static final String DATE = "date";
        public static final String ADDRESS = "address";
        public static final String REQUEST = "request";

        public static final String DEFAULT_SORT_ORDER = DATE + " ASC";
    }

    @Deprecated
    public static class Statistics implements BaseColumns {
        public static final Uri CONTENT_URI = Uri.parse(
            "content://" + AUTHORITY + "/" + Tables.STATISTICS);

        public static final String CONTENT_TYPE =
        "vnd.android.cursor.dir/vnd.personalkeeper.statistics";
        public static final String CONTENT_ITEM_TYPE =
        "vnd.android.cursor.item/vnd.personalkeeper.statistics";

        public static final String TYPE = "type";
        public static final String DATE = "date";
        public static final String DISTANCE = "distance";

        public static final String DEFAULT_SORT_ORDER = DATE + " ASC";
    }

    public static class TrackingSession implements BaseColumns {
        public static final Uri CONTENT_URI = Uri.parse(
            "content://" + AUTHORITY + "/" + Tables.TRACKING_SESSION);

        public static final String CONTENT_TYPE =
        "vnd.android.cursor.dir/vnd.personalkeeper.session";
        public static final String CONTENT_ITEM_TYPE =
        "vnd.android.cursor.item/vnd.personalkeeper.session";

        public static final String START_DATE = "start_date";
        public static final String END_DATE = "end_date";
        public static final String DISTANCE = "distance";

        public static final String DEFAULT_SORT_ORDER = START_DATE + " ASC";

        public static class Summary {
            public static final Uri CONTENT_URI = Uri.parse(
                "content://" + AUTHORITY + "/summary");
        }
    }

    static class DatabaseHelper extends SQLiteOpenHelper {
        private static final String TAG = DatabaseHelper.class.toString();

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        private static void createIndex(SQLiteDatabase db, String idxName, String table,
        String[] columns) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < columns.length; i++) {
                if (i != 0)
                    sb.append(", ");
                sb.append(columns[i]);
            }
            db.execSQL(String.format("CREATE INDEX %s ON %s(%s)",
            idxName + "_" + table,
            table,
            sb.toString()));
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            Log.i(TAG, "Creating database");

            // location_history
            db.execSQL("CREATE TABLE " + Tables.LOCATION_HISTORY + " (" +
            LocationHistory._ID       + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            LocationHistory.LATITUDE  + " REAL NOT NULL, " +
            LocationHistory.LONGITUDE + " REAL NOT NULL, " +
            LocationHistory.DATE      + " INTEGER NOT NULL)"
                      );
            createIndex(db,
                "idx_date",
                Tables.LOCATION_HISTORY,
                new String[] { LocationHistory.DATE });

            // TODO: delete
            // markers
            db.execSQL("CREATE TABLE " + Tables.MARKERS + " (" +
            Markers._ID          + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            Markers.NAME         + " TEXT, " +
            Markers.LATITUDE     + " REAL NOT NULL, " +
            Markers.LONGITUDE    + " REAL NOT NULL, " +
            Markers.ADDRESS      + " TEXT, " +
            Markers.REQUEST      + " INTEGER NOT NULL," +
            Markers.DATE         + " INTEGER NOT NULL)"
                      );
            createIndex(db,
                "idx_date",
                Tables.MARKERS,
                new String[] { Markers.DATE });

            // notes
            db.execSQL("CREATE TABLE " + Tables.NOTES + " (" +
            Notes._ID         + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            Notes.TITLE       + " TEXT NOT NULL, " +
            Notes.DESCRIPTION + " TEXT, " +
            Notes.LATITUDE    + " REAL NOT NULL, " +
            Notes.LONGITUDE   + " REAL NOT NULL, " +
            Notes.DATE        + " INTEGER NOT NULL, " +
            Notes.IMAGE_PATH  + " TEXT)"
                      );
            createIndex(db,
                "idx_date",
                Tables.NOTES,
                new String[] { Notes.DATE });

            //statistics
            db.execSQL("CREATE TABLE " + Tables.STATISTICS + " (" +
            Statistics._ID      + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            Statistics.TYPE     + " TEXT NOT NULL, " +
            Statistics.DATE     + " INTEGER NOT NULL, " +
            Statistics.DISTANCE + " REAL NOT NULL)"
                      );
                createIndex(db,
                "idx_date",
                Tables.STATISTICS,
                new String[] { Statistics.DATE });

            // TrackingSession
            db.execSQL("CREATE TABLE " + Tables.TRACKING_SESSION + " (" +
                TrackingSession._ID        + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                TrackingSession.START_DATE + " INTEGER NOT NULL, " +
                TrackingSession.END_DATE   + " INTEGER NOT NULL, " +
                TrackingSession.DISTANCE   + " REAL NOT NULL)"
                      );
            createIndex(db,
                "idx_date",
                Tables.TRACKING_SESSION,
                new String[] { TrackingSession.START_DATE, TrackingSession.END_DATE });
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Recreating all tables. All data will be lost");
            for (String table : Tables.ALL_TABLES)
                db.execSQL("DROP TABLE IF EXISTS " + table);
            onCreate(db);
        }
    }

    private static final int LOCATIONS           = 0;
    private static final int LOCATION_ID         = 1;
    private static final int MARKERS             = 2;
    private static final int MARKER_ID           = 3;
    private static final int NOTES               = 4;
    private static final int NOTE_ID             = 5;
    private static final int STATISTICS          = 6;
    private static final int STATISTICS_ID       = 7;
    private static final int TRACKING_SESSION    = 8;
    private static final int TRACKING_SESSION_ID = 9;
    private static final int SUMMARY_DISTANCE    = 10;
    private static UriMatcher mUriMatcher;
    private DatabaseHelper mOpenHelper;

    static {
        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        mUriMatcher.addURI(AUTHORITY, Tables.LOCATION_HISTORY, LOCATIONS);
        mUriMatcher.addURI(AUTHORITY, Tables.LOCATION_HISTORY + "/#", LOCATION_ID);
        mUriMatcher.addURI(AUTHORITY, Tables.MARKERS, MARKERS);
        mUriMatcher.addURI(AUTHORITY, Tables.MARKERS + "/#", MARKER_ID);
        mUriMatcher.addURI(AUTHORITY, Tables.NOTES, NOTES);
        mUriMatcher.addURI(AUTHORITY, Tables.NOTES + "/#", NOTE_ID);
        mUriMatcher.addURI(AUTHORITY, Tables.STATISTICS, STATISTICS);
        mUriMatcher.addURI(AUTHORITY, Tables.STATISTICS + "/#", STATISTICS_ID);
        mUriMatcher.addURI(AUTHORITY, Tables.TRACKING_SESSION, TRACKING_SESSION);
        mUriMatcher.addURI(AUTHORITY, Tables.TRACKING_SESSION + "/#", TRACKING_SESSION_ID);
        mUriMatcher.addURI(AUTHORITY, "summary/", SUMMARY_DISTANCE);
    }

    @Override
    public boolean onCreate() {
        mOpenHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Override
    public String getType(Uri uri) {
        switch (mUriMatcher.match(uri)) {
        case LOCATION_ID:
            return LocationHistory.CONTENT_ITEM_TYPE;

        case LOCATIONS:
            return LocationHistory.CONTENT_TYPE;

        case MARKER_ID:
            return Markers.CONTENT_ITEM_TYPE;

        case MARKERS:
            return Markers.CONTENT_TYPE;

        case NOTE_ID:
            return Notes.CONTENT_ITEM_TYPE;

        case NOTES:
            return Notes.CONTENT_TYPE;

        case STATISTICS:
            return Statistics.CONTENT_TYPE;

        case STATISTICS_ID:
            return Statistics.CONTENT_ITEM_TYPE;

        case TRACKING_SESSION:
            return TrackingSession.CONTENT_TYPE;

        case TRACKING_SESSION_ID:
            return TrackingSession.CONTENT_ITEM_TYPE;

        default:
            throw new IllegalArgumentException("Unknown uri: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        ContentValues values = (initialValues != null) ? initialValues : new ContentValues();
        String table = tableFromUri(uri);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        long rowId = db.insert(table, null, values);
        if (rowId > 0) {
            Uri resultUri = ContentUris.withAppendedId(Markers.CONTENT_URI, rowId);
            getContext().getContentResolver().notifyChange(resultUri, null);
            return resultUri;
        } else
            return null;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrderArgument) {
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();

        if (mUriMatcher.match(uri) == SUMMARY_DISTANCE) {
            String rawQuery =
            "SELECT " +
            "SUM(" + TrackingSession.DISTANCE + "), " +
            "SUM(" + TrackingSession.END_DATE + " - " + TrackingSession.START_DATE + ") " +
            "FROM " + Tables.TRACKING_SESSION;
            if (selection != null)
                rawQuery += " WHERE " + selection;
            return db.rawQuery(rawQuery, selectionArgs);
        }

        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

        qb.setTables(tableFromUriWithId(uri));
        String where = whereClauseFromUriWithid(uri);
        if (where != null)
            qb.appendWhere(where);

        // If no sort order is specified use the default
        String orderBy;
        if (TextUtils.isEmpty(sortOrderArgument))
            orderBy = defaultSortOrderFromUriWithId(uri);
        else
            orderBy = sortOrderArgument;

        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);

        // Tell the cursor what uri to watch, so it knows when its
        // source data changes
        cursor.setNotificationUri(getContext().getContentResolver(), uri);

        return cursor;
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

        String table = tableFromUriWithId(uri);
        String whereClause = concatWhereClauses(whereClauseFromUriWithid(uri), selection);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.update(table, values, whereClause, selectionArgs);

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        String table = tableFromUriWithId(uri);
        String whereClause = concatWhereClauses(whereClauseFromUriWithid(uri), selection);

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.delete(table, whereClause, selectionArgs);

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }


    private static String concatWhereClauses(String clause1, String clause2) {
        if (clause1 == null)
            return clause2;
        if (clause2 == null)
            return clause1;
        return "(" + clause1 + ") AND (" + clause2 + ")";
    }

    private static String whereClauseFromUriWithid(Uri uri) {
        String whereClause;
        switch (mUriMatcher.match(uri)) {
        case LOCATION_ID:
            whereClause = LocationHistory._ID;
            break;

        case NOTE_ID:
            whereClause = Notes._ID;
            break;

        case MARKER_ID:
            whereClause = Markers._ID;
            break;

        case STATISTICS_ID:
            whereClause = Statistics._ID;
            break;

        case TRACKING_SESSION_ID:
            whereClause = TrackingSession._ID;
            break;

        default:
            whereClause = null;
        }

        if (whereClause != null)
            whereClause = "(" + whereClause + "=" + uri.getPathSegments().get(1) + ")";

        return whereClause;
    }
    private static String defaultSortOrderFromUriWithId(Uri uri) {
        switch (mUriMatcher.match(uri)) {
        case LOCATION_ID:
        case LOCATIONS:
            return LocationHistory.DEFAULT_SORT_ORDER;

        case NOTE_ID:
        case NOTES:
            return Notes.DEFAULT_SORT_ORDER;

        case MARKER_ID:
        case MARKERS:
            return Markers.DEFAULT_SORT_ORDER;

        case STATISTICS_ID:
        case STATISTICS:
            return Statistics.DEFAULT_SORT_ORDER;

        case TRACKING_SESSION_ID:
        case TRACKING_SESSION:
            return TrackingSession.DEFAULT_SORT_ORDER;

        default:
            throw new IllegalArgumentException("Unknown uri: " + uri);
        }
    }
    private static String tableFromUriWithId(Uri uri) {
        switch (mUriMatcher.match(uri)) {
        case LOCATION_ID:
        case LOCATIONS:
            return Tables.LOCATION_HISTORY;

        case NOTE_ID:
        case NOTES:
            return Tables.NOTES;

        case MARKER_ID:
        case MARKERS:
            return Tables.MARKERS;

        case STATISTICS_ID:
        case STATISTICS:
            return Tables.STATISTICS;

        case TRACKING_SESSION_ID:
        case TRACKING_SESSION:
        case SUMMARY_DISTANCE:
            return Tables.TRACKING_SESSION;

        default:
            throw new IllegalArgumentException("Unknown uri: " + uri);
        }
    }
    private static String tableFromUri(Uri uri) {
        switch (mUriMatcher.match(uri)) {
        case LOCATIONS:
            return Tables.LOCATION_HISTORY;

        case NOTES:
            return Tables.NOTES;

        case MARKERS:
            return Tables.MARKERS;

        case STATISTICS:
            return Tables.STATISTICS;

        case TRACKING_SESSION:
        case SUMMARY_DISTANCE:
            return Tables.TRACKING_SESSION;

        default:
            throw new IllegalArgumentException("Unknown uri: " + uri);
        }
    }
}
